{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "#%%\r\n",
    "from vnpy_ctastrategy.backtesting import BacktestingEngine, OptimizationSetting\r\n",
    "from vnpy_ctastrategy.strategies.turtle_signal_strategy import TurtleSignalStrategy\r\n",
    "from datetime import datetime"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "#%%\r\n",
    "engine = BacktestingEngine()\r\n",
    "engine.set_parameters(\r\n",
    "    vt_symbol=\"XBTUSD.BITMEX\",\r\n",
    "    interval=\"1h\",\r\n",
    "    start=datetime(2017, 1, 1),\r\n",
    "    end=datetime(2019, 11, 30),\r\n",
    "    rate=1/10000,\r\n",
    "    slippage=0.5,\r\n",
    "    size=1,\r\n",
    "    pricetick=0.5,\r\n",
    "    capital=1_000_000,\r\n",
    ")\r\n",
    "engine.add_strategy(TurtleSignalStrategy, {})"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "#%%\n",
    "engine.load_data()\n",
    "engine.run_backtesting()\n",
    "df = engine.calculate_result()\n",
    "engine.calculate_statistics()\n",
    "engine.show_chart()"
   ],
   "outputs": [],
   "metadata": {
    "scrolled": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "import pandas as pd\n",
    "from datetime import datetime\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "pd.set_option('mode.chained_assignment', None)\n",
    "\n",
    "\n",
    "def calculate_trades_result(trades):\n",
    "    \"\"\"\n",
    "    Deal with trade data\n",
    "    \"\"\"\n",
    "    dt, direction, offset, price, volume = [], [], [], [], []\n",
    "    for i in trades.values():\n",
    "        dt.append(i.datetime)\n",
    "        direction.append(i.direction.value)\n",
    "        offset.append(i.offset.value)\n",
    "        price.append(i.price)\n",
    "        volume.append(i.volume)\n",
    "\n",
    "    # Generate DataFrame with datetime, direction, offset, price, volume\n",
    "    df = pd.DataFrame()\n",
    "    df[\"direction\"] = direction\n",
    "    df[\"offset\"] = offset\n",
    "    df[\"price\"] = price\n",
    "    df[\"volume\"] = volume\n",
    "\n",
    "    df[\"current_time\"] = dt\n",
    "    df[\"last_time\"] = df[\"current_time\"].shift(1)\n",
    "\n",
    "    # Calculate trade amount\n",
    "    df[\"amount\"] = df[\"price\"] * df[\"volume\"]\n",
    "    df[\"acum_amount\"] = df[\"amount\"].cumsum()\n",
    "\n",
    "    # Calculate pos, net pos(with direction), acumluation pos(with direction)\n",
    "    def calculate_pos(df):\n",
    "        if df[\"direction\"] == \"多\":\n",
    "            result = df[\"volume\"]\n",
    "        else:\n",
    "            result = - df[\"volume\"]\n",
    "\n",
    "        return result\n",
    "    df[\"pos\"] = df.apply(calculate_pos, axis=1)\n",
    "\n",
    "    df[\"net_pos\"] = df[\"pos\"].cumsum()\n",
    "    df[\"acum_pos\"] = df[\"volume\"].cumsum()\n",
    "\n",
    "    # Calculate trade result, acumulation result\n",
    "    # ej: trade result(buy->sell) means (new price - old price) * volume\n",
    "    df[\"result\"] = -1 * df[\"pos\"] * df[\"price\"]\n",
    "    df[\"acum_result\"] = df[\"result\"].cumsum()\n",
    "\n",
    "    # Filter column data when net pos comes to zero\n",
    "    def get_acum_trade_result(df):\n",
    "        if df[\"net_pos\"] == 0:\n",
    "            return df[\"acum_result\"]\n",
    "    df[\"acum_trade_result\"] = df.apply(get_acum_trade_result, axis=1)\n",
    "\n",
    "    def get_acum_trade_volume(df):\n",
    "        if df[\"net_pos\"] == 0:\n",
    "            return df[\"acum_pos\"]\n",
    "    df[\"acum_trade_volume\"] = df.apply(get_acum_trade_volume, axis=1)   \n",
    "\n",
    "    def get_acum_trade_duration(df):\n",
    "        if df[\"net_pos\"] == 0:\n",
    "            return df[\"current_time\"] - df[\"last_time\"]\n",
    "    df[\"acum_trade_duration\"] = df.apply(get_acum_trade_duration, axis=1)  \n",
    "\n",
    "    def get_acum_trade_amount(df):\n",
    "        if df[\"net_pos\"] == 0:\n",
    "            return df[\"acum_amount\"]\n",
    "    df[\"acum_trade_amount\"] = df.apply(get_acum_trade_amount, axis=1) \n",
    "\n",
    "    # Select row data with net pos equil to zero     \n",
    "    df = df.dropna()\n",
    "\n",
    "    return df\n",
    "\n",
    "\n",
    "def generate_trade_df(trades, size, rate, slippage, capital):\n",
    "    \"\"\"\n",
    "    Calculate trade result from increment\n",
    "    \"\"\"\n",
    "    df = calculate_trades_result(trades)\n",
    "\n",
    "    trade_df = pd.DataFrame()\n",
    "    trade_df[\"close_direction\"] = df[\"direction\"]\n",
    "    trade_df[\"close_time\"] = df[\"current_time\"]\n",
    "    trade_df[\"close_price\"] = df[\"price\"]\n",
    "    trade_df[\"pnl\"] = df[\"acum_trade_result\"] - \\\n",
    "        df[\"acum_trade_result\"].shift(1).fillna(0)\n",
    "    \n",
    "    trade_df[\"volume\"] = df[\"acum_trade_volume\"] - \\\n",
    "        df[\"acum_trade_volume\"].shift(1).fillna(0)\n",
    "    trade_df[\"duration\"] = df[\"current_time\"] - \\\n",
    "        df[\"last_time\"]\n",
    "    trade_df[\"turnover\"] = df[\"acum_trade_amount\"] - \\\n",
    "        df[\"acum_trade_amount\"].shift(1).fillna(0)\n",
    "    \n",
    "    trade_df[\"commission\"] = trade_df[\"turnover\"] * rate\n",
    "    trade_df[\"slipping\"] = trade_df[\"volume\"] * size * slippage\n",
    "\n",
    "    trade_df[\"net_pnl\"] = trade_df[\"pnl\"] - \\\n",
    "        trade_df[\"commission\"] - trade_df[\"slipping\"]\n",
    "\n",
    "    result = calculate_base_net_pnl(trade_df, capital)\n",
    "    return result\n",
    "\n",
    "\n",
    "def calculate_base_net_pnl(df, capital):\n",
    "    \"\"\"\n",
    "    Calculate statistic base on net pnl\n",
    "    \"\"\"\n",
    "    df[\"acum_pnl\"] = df[\"net_pnl\"].cumsum()\n",
    "    df[\"balance\"] = df[\"acum_pnl\"] + capital\n",
    "    df[\"return\"] = np.log(\n",
    "        df[\"balance\"] / df[\"balance\"].shift(1)\n",
    "        ).fillna(0)\n",
    "    df[\"highlevel\"] = (\n",
    "        df[\"balance\"].rolling(\n",
    "            min_periods=1, window=len(df), center=False).max()\n",
    "    )\n",
    "    df[\"drawdown\"] = df[\"balance\"] - df[\"highlevel\"]\n",
    "    df[\"ddpercent\"] = df[\"drawdown\"] / df[\"highlevel\"] * 100\n",
    "\n",
    "    df.reset_index(drop=True, inplace=True)\n",
    "    \n",
    "    return df\n",
    "\n",
    "\n",
    "def buy2sell(df, capital):\n",
    "    \"\"\"\n",
    "    Generate DataFrame with only trade from buy to sell\n",
    "    \"\"\"\n",
    "    buy2sell = df[df[\"close_direction\"] == \"空\"]\n",
    "    result = calculate_base_net_pnl(buy2sell, capital)\n",
    "    return result\n",
    "\n",
    "\n",
    "def short2cover(df, capital):\n",
    "    \"\"\"\n",
    "    Generate DataFrame with only trade from short to cover\n",
    "    \"\"\"\n",
    "    short2cover = df[df[\"close_direction\"] == \"多\"]\n",
    "    result = calculate_base_net_pnl(short2cover, capital)\n",
    "    return result\n",
    "\n",
    "\n",
    "def statistics_trade_result(df, capital, show_chart=True):\n",
    "    \"\"\"\"\"\"\n",
    "    end_balance = df[\"balance\"].iloc[-1]\n",
    "    max_drawdown = df[\"drawdown\"].min()\n",
    "    max_ddpercent = df[\"ddpercent\"].min()\n",
    "\n",
    "    pnl_medio = df[\"net_pnl\"].mean()\n",
    "    trade_count = len(df)\n",
    "    duration_medio = df[\"duration\"].mean().total_seconds()/3600\n",
    "    commission_medio = df[\"commission\"].mean()\n",
    "    slipping_medio = df[\"slipping\"].mean()\n",
    "\n",
    "    win = df[df[\"net_pnl\"] > 0]\n",
    "    win_amount = win[\"net_pnl\"].sum()\n",
    "    win_pnl_medio = win[\"net_pnl\"].mean()\n",
    "    win_duration_medio = win[\"duration\"].mean().total_seconds()/3600\n",
    "    win_count = len(win)\n",
    "\n",
    "    loss = df[df[\"net_pnl\"] < 0]\n",
    "    loss_amount = loss[\"net_pnl\"].sum()\n",
    "    loss_pnl_medio = loss[\"net_pnl\"].mean()\n",
    "    loss_duration_medio = loss[\"duration\"].mean().total_seconds()/3600\n",
    "    loss_count = len(loss)\n",
    "\n",
    "    winning_rate = win_count / trade_count\n",
    "    win_loss_pnl_ratio = - win_pnl_medio / loss_pnl_medio\n",
    "\n",
    "    total_return = (end_balance / capital - 1) * 100\n",
    "    return_drawdown_ratio = -total_return / max_ddpercent\n",
    "\n",
    "    output(f\"起始资金：\\t{capital:,.2f}\")\n",
    "    output(f\"结束资金：\\t{end_balance:,.2f}\")\n",
    "    output(f\"总收益率：\\t{total_return:,.2f}%\")\n",
    "    output(f\"最大回撤: \\t{max_drawdown:,.2f}\")\n",
    "    output(f\"百分比最大回撤: {max_ddpercent:,.2f}%\")\n",
    "    output(f\"收益回撤比：\\t{return_drawdown_ratio:,.2f}\")\n",
    "\n",
    "    output(f\"总成交次数:\\t{trade_count}\")\n",
    "    output(f\"盈利成交次数:\\t{win_count}\")\n",
    "    output(f\"亏损成交次数:\\t{loss_count}\")\n",
    "    output(f\"胜率:\\t\\t{winning_rate:,.2f}\")\n",
    "    output(f\"盈亏比:\\t\\t{win_loss_pnl_ratio:,.2f}\")\n",
    "\n",
    "    output(f\"平均每笔盈亏:\\t{pnl_medio:,.2f}\")\n",
    "    output(f\"平均持仓小时:\\t{duration_medio:,.2f}\")\n",
    "    output(f\"平均每笔手续费:\\t{commission_medio:,.2f}\")\n",
    "    output(f\"平均每笔滑点:\\t{slipping_medio:,.2f}\")\n",
    "\n",
    "    output(f\"总盈利金额:\\t{win_amount:,.2f}\")\n",
    "    output(f\"盈利交易均值:\\t{win_pnl_medio:,.2f}\")\n",
    "    output(f\"盈利持仓小时:\\t{win_duration_medio:,.2f}\")\n",
    "\n",
    "    output(f\"总亏损金额:\\t{loss_amount:,.2f}\")\n",
    "    output(f\"亏损交易均值:\\t{loss_pnl_medio:,.2f}\")\n",
    "    output(f\"亏损持仓小时:\\t{loss_duration_medio:,.2f}\")\n",
    "\n",
    "    if not show_chart:\n",
    "        return\n",
    "\n",
    "    plt.figure(figsize=(10, 12))\n",
    "\n",
    "    acum_pnl_plot = plt.subplot(3, 1, 1)\n",
    "    acum_pnl_plot.set_title(\"Balance Plot\")\n",
    "    df[\"balance\"].plot(legend=True)\n",
    "\n",
    "    pnl_plot = plt.subplot(3, 1, 2)\n",
    "    pnl_plot.set_title(\"Pnl Per Trade\")\n",
    "    df[\"net_pnl\"].plot(legend=True)\n",
    "\n",
    "    distribution_plot = plt.subplot(3, 1, 3)\n",
    "    distribution_plot.set_title(\"Trade Pnl Distribution\")\n",
    "    df[\"net_pnl\"].hist(bins=100)\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "def output(msg):\n",
    "    \"\"\"\n",
    "    Output message with datetime.\n",
    "    \"\"\"\n",
    "    print(f\"{datetime.now()}\\t{msg}\")\n",
    "\n",
    "\n",
    "def exhaust_trade_result(\n",
    "    trades, \n",
    "    size: int = 10, \n",
    "    rate: float = 0.0, \n",
    "    slippage: float = 0.0, \n",
    "    capital: int = 1000000,\n",
    "    show_long_short_condition=True\n",
    "    ):\n",
    "    \"\"\"\n",
    "    Exhaust all trade result.\n",
    "    \"\"\"\n",
    "\n",
    "    total_trades = generate_trade_df(trades, size, rate, slippage, capital)\n",
    "    statistics_trade_result(total_trades, capital)\n",
    "\n",
    "    if not show_long_short_condition:\n",
    "        return\n",
    "    long_trades = buy2sell(total_trades, capital)\n",
    "    short_trades = short2cover(total_trades, capital)\n",
    "\n",
    "    output(\"-----------------------\")\n",
    "    output(\"纯多头交易\")\n",
    "    statistics_trade_result(long_trades, capital)\n",
    "\n",
    "    output(\"-----------------------\")\n",
    "    output(\"纯空头交易\")\n",
    "    statistics_trade_result(short_trades, capital)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [
    "exhaust_trade_result(engine.trades,size=1, rate=8/10000, slippage=0.5)"
   ],
   "outputs": [],
   "metadata": {
    "scrolled": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "source": [],
   "outputs": [],
   "metadata": {}
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}